#ifndef TREE_HPP
#define TREE_HPP

#include "HW7.h"
#include <map>
#include <string>
#include <iostream>
#include <list>
#include <deque>
#include "Vector3.hpp"
#include "Matrix4.hpp"


using namespace std;

struct turtleState {
	Vector3 pos;
	Vector3 dir;
	// (X,Y,Z,Angle_U,Angle_L,Angle_H).
} ;

class Tree {
	private:
	public:
		
		map<char, deque<string> > rules;
		string axiom;
		int iterations;
		int angle;
		float thickness;
		int length;
		float shrinkFactor;
		turtleState tState;
		list<turtleState> tStack;
		list<Vector3> points;
		list< list<Vector3> > pointsList;
		string actions;
		
		Tree(string axiom, map<char, deque<string> > rules,int iterations){
			defaultAttributes();
			this->iterations=iterations;
			this->axiom=axiom;
			this->rules=rules;
			actions =iterate(iterations,axiom,rules);
			defaultTurtleState();
			produceTree(actions);
		}
		Tree(string axiom,int iterations){
			defaultAttributes();
			this->iterations=iterations;
			this->axiom=axiom;
			defaultRules();
			actions =iterate(iterations,axiom,rules);
			defaultTurtleState();
			produceTree(actions);
		}
		Tree(){
			iterations=3;
			axiom="A";
			defaultAttributes();
			defaultRules();
			actions =iterate(iterations,axiom,rules);
			cout << "Actions: " << actions << "\n	"<< endl;
			defaultTurtleState();
			produceTree(actions);
		}
		void defaultAttributes(){
			angle=20;
			length=2;
			thickness=.5;
			shrinkFactor=.6;
		}
		void defaultRules(){
			// rules['F']="FF-[-F+F]+[+F-F]^A";
			// 	rules['A']="[^F&F]&[&F^F]";
			
			deque<string> rulesA;
			rulesA.push_back("F[-F^&BA][^^^^FBA][+^^FBA][&BAF]");
			rulesA.push_back("F[-&&FBA][^^FBA][++&&&&BFA][&BAF]");
			rulesA.push_back("F[-FBA][^^FBA][+&&&F-BA][&ABF]");
			rulesA.push_back("F[-FBA][^^^^^FBA][+&&&&FB&A][&A+FB]");
			rulesA.push_back("F[+FBA][-FBA][^FBA][&FBA]");
			//rulesA.push_back("F[+.BFA][-.BFA][^.BFA][&.BFA]");
			rules['A'] = rulesA;
					
		}
		void defaultTurtleState(){
			tState.pos=Vector3(0,0,0);
			tState.dir=Vector3(0,1,0);
			//points.push_back(tState.pos);
			tStack.push_back(tState);
		}
		string produceActions(string axiom,map<char, deque<string> > rules){
			string output = "";
			//srand ( time(NULL) );
		  for (int i=0; i < axiom.length(); i++)
		  {
				if (rules.count(axiom[i]) >0){
						int rsize=(int) rules[axiom[i]].size();
						int index=rand() % rsize;
						//printf("index:%d\n",index);
						output=output + rules[axiom[i]].at(index);
				}
				else
					output=output + axiom[i];
		  }
			//cout << "String: " << output << "\n	"<< endl;
			return output;
		}
		string iterate(int n,string axiom, map<char, deque<string> > rules){
			
	    if (n>0){
				axiom = produceActions(axiom,rules);
				return iterate(n-1,axiom,rules);
			}
			return axiom;
		}
		void drawCylinder(float base, float top, float height, int slices, int stacks){
			GLUquadric* qobj;
			qobj = gluNewQuadric();
			gluQuadricNormals(qobj, GLU_SMOOTH);
			gluCylinder(qobj, base, top, height, slices, stacks);
		}
		void drawTreeCylinders(){
			Vector3 step;
			Matrix4 rot;
			Vector3 newdir;
			float th=thickness;
			float len=length;
			
			glColor3f(.54,.23,0.3);
			//printf("(%f,%f,%f)\n",tState.dir.x,tState.dir.y,tState.dir.z);
			for (int i=0; i < actions.length(); i++)
		  {
				switch (actions[i])
				{
				  case 'F':
						//Move forward a step of length d. A line segment between points (X,Y,Z) and (X',Y',Z') is drawn.
						glTranslatef(0,len,0);
						glPushMatrix();
						glRotatef(90,1,0,0);
						drawCylinder(th*shrinkFactor, th, len, 3, 3);
						glPopMatrix();			
						break;
					case 'A':
						//leaf type 1
						glPushMatrix();
						glColor3f(0,1,0);
						glRotatef(90,1,0,0);
						drawCylinder(.05, .2, .2, 3, 3);
						glColor3f(.54,.23,0.3);
						glPopMatrix();			
						break;
					case 'B':
						//leaf type 2
						glPushMatrix();
						glColor3f(.1,.6,.1);
						glRotatef(80,1,1,0);
						drawCylinder(.1, .4, .15, 3, 3);
						glColor3f(.54,.23,0.3);
						glPopMatrix();			
						break;
				  case '[':
				     //push the current state,
						glPushMatrix();
						len=len*(1.0*shrinkFactor);
						th=th*(1.0*shrinkFactor);
						break;
					case ']':
						//pop the current state
						glPopMatrix();
						len=len/shrinkFactor;
						th=th/shrinkFactor;
						break;
					case '+':
						//Turn left by angle Delta, Using rotation matrix R_U(Delta)
						glRotatef( angle,0,0,1);
						break;
					case '-':
						//Turn right by angle Delta, Using rotation matrix R_U(-Delta)
						glRotatef( -angle,0,0,1);
						break;
					case '&':
						//Pitch down by angle Delta, Using rotation matrix R_L(Delta)
						glRotatef( angle,0,1,0);
						break;
					case '^':
						//Pitch up by angle Delta, Using rotation matrix R_L(-Delta)
						glRotatef( -angle,0,1,0);
						break;
					case '<':
						//Roll left by angle Delta, Using rotation matrix R_H(Delta)
						glRotatef( angle,1,0,0);
						break;
					case '>':
						//Roll right by angle Delta, Using rotation matrix R_H(-Delta)
						glRotatef( -angle,1,0,0);
						break;
					case '|':
						//Turn around, Using rotation matrix R_H(180)
						glRotatef(180,1,0,0);
					break;
				  default:
					break;
				}
			}
			
			pointsList.push_back(points);
		}
		void produceTree(string actions){
			Vector3 step;
			Matrix4 rot;
			Vector3 newdir;
			list<Vector3>::iterator it1;
			list<Vector3>::iterator it2;
			
			//printf("(%f,%f,%f)\n",tState.dir.x,tState.dir.y,tState.dir.z);
			for (int i=0; i < actions.length(); i++)
		  {
				list<Vector3> pointscp (points);
				it1=points.begin();
				it2=points.end();
			
				switch (actions[i])
				{
					case '.':
						//make length smaller
						//length=length*.99999999999999;
						break;
				  case 'F':
						//Move forward a step of length d. A line segment between points (X,Y,Z) and (X',Y',Z') is drawn.
						points.push_back(tState.pos);
						step=(tState.dir).mult(length);
						tState.pos=tState.pos+step;
						points.push_back(tState.pos);
						break;
				  case '[':
				     //push the current state,
						tStack.push_back(tState);
						break;
					case ']':
						//pop the current state
						tState=tStack.back();
						tStack.pop_back();
						pointsList.push_back(pointscp);
						points.erase (it1,it2);
						break;
					case '+':
						//Turn left by angle Delta, Using rotation matrix R_U(Delta)
						rot.rotateZ(angle);
						newdir=rot.mult3(tState.dir);
						tState.dir=newdir.normalize();
						//glRotatef( angle,0,0,1);
						break;
					case '-':
						//Turn right by angle Delta, Using rotation matrix R_U(-Delta)
						rot.rotateZ(-angle);
						newdir=rot.mult3(tState.dir);
						tState.dir=newdir.normalize();
						//glRotatef( -angle,0,0,1);
						break;
					case '&':
						//Pitch down by angle Delta, Using rotation matrix R_L(Delta)
						rot.rotateY(angle);
						newdir=rot.mult3(tState.dir);
						tState.dir=newdir.normalize();
						//glRotatef( angle,0,1,0);
						break;
					case '^':
						//Pitch up by angle Delta, Using rotation matrix R_L(-Delta)
						rot.rotateY(-angle);
						newdir=rot.mult3(tState.dir);
						tState.dir=newdir.normalize();
						//glRotatef( -angle,0,1,0);
						break;
					case '<':
						//Roll left by angle Delta, Using rotation matrix R_H(Delta)
						rot.rotateX(angle);
						newdir=rot.mult3(tState.dir);
						tState.dir=newdir.normalize();
						//glRotatef( angle,1,0,0);
						break;
					case '>':
						//Roll right by angle Delta, Using rotation matrix R_H(-Delta)
						rot.rotateX(-angle);
						newdir=rot.mult3(tState.dir);
						tState.dir=newdir.normalize();
						//glRotatef( -angle,1,0,0);
						break;
					case '|':
						//Turn around, Using rotation matrix R_H(180)
						rot.rotateX(180);
						newdir=rot.mult3(tState.dir);
						tState.dir=newdir.normalize();
						//glRotatef(180,1,0,0);
					break;
				  default:
					break;
				}
			}
			
			pointsList.push_back(points);
			
		}
		
};

#endif